package com.movie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.movie.entity.Country;
import com.movie.entity.ESMovie;
import com.movie.entity.Movie;
import com.movie.entity.Type;
import com.movie.mapper.MovieMapper;
import com.movie.service.IMovieService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.movie.vo.PageResultVO;
import com.movie.vo.Result;
import com.movie.vo.SearchResultVo;
import net.bytebuddy.asm.Advice;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author lxx
 * @since 2021-04-13
 */
@Service
public class MovieServiceImpl extends ServiceImpl<MovieMapper, Movie> implements IMovieService {

    @Autowired(required = false)
    private MovieMapper movieMapper;

    @Autowired
    private ElasticsearchRestTemplate template;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 初始化电影演员数据到ES中
     * @throws Exception
     */
    @Override
    public void initMovie() throws Exception {
        List<Movie> movieList = movieMapper.initMovie();
        System.out.println(movieList);
        template.deleteIndex(ESMovie.class);
        template.createIndex(ESMovie.class);
        template.putMapping(ESMovie.class);
        movieList.forEach(spu->{
            ESMovie esMovie=new ESMovie();
            esMovie.setMovieId(spu.getMovieId());
            esMovie.setMovieName(spu.getMovieName());
            esMovie.setMoviePic(spu.getMoviePic());
            esMovie.setState(spu.getState());
            esMovie.setPrice(spu.getPrice());
            esMovie.setScore(spu.getScore());

            esMovie.setUploadTime(spu.getUploadTime());


            esMovie.setTotalTime(spu.getTotalTime());
            esMovie.setStartName(spu.getStar().getStarName());
            esMovie.setSex(spu.getStar().getSex());
            esMovie.setBirth(spu.getStar().getBirth());
            esMovie.setType(spu.getStar().getType());//演员类型
            esMovie.setNationlity(spu.getStar().getNationality());
            List<String> typeName =new ArrayList<>();
            List<String> typeNameList = spu.getType().getTypeNameList();
            StringBuilder typeNameBuilder=new StringBuilder();
            typeNameList.forEach(type->{
                typeNameBuilder.append(type);
            });
            typeNameBuilder.append("/恐怖");
            String TypetoString = typeNameBuilder.toString();
            esMovie.setTypeName(TypetoString);//电影类型
            esMovie.setCountryName(spu.getCountry().getCountryName());
            template.save(esMovie);
        });
    }

    /**
     * 根据关键字从Elastic中查询数据
     * @param search
     * @return
     * @throws Exception
     */
    @Override
    public PageResultVO<ESMovie> searchMovieByKeyWord(Map search) throws Exception {
        //1.构建查询条件
        BoolQueryBuilder boolQueryBuilder= QueryBuilders.boolQuery();
        System.out.println(search.get("keyword"));//前端自定义的keyword
        Object keyword = search.get("keyword");
        //判断 keyword是否为空
        if (!StringUtils.isEmpty(keyword))
        {
            //如果不为空 对关键字进行匹配
            //2.1匹配关键字movieName
            MatchQueryBuilder movieNameMatchQueryBuilder=QueryBuilders
                    .matchQuery("movieName",keyword);
            //2.2匹配关键字startName
            MatchQueryBuilder startNameMatchQueryBuilder=QueryBuilders
                    .matchQuery("startName",keyword);
            boolQueryBuilder.should(movieNameMatchQueryBuilder);
            boolQueryBuilder.should(startNameMatchQueryBuilder);
        }
        //根据电影类型（typeName）和区域（countryName）过滤
        if (!StringUtils.isEmpty(search.get("typeNameFilter"))){
            //如果电影类型不为空
            MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("typeName", search.get("typeNameFilter"));
            boolQueryBuilder.should(matchQueryBuilder);
        }
        if (!StringUtils.isEmpty(search.get("countryNameFilter"))){
            //如果电影类型不为空
            TermQueryBuilder countryNameQueryBuild=QueryBuilders.termQuery("countryName",search.get("countryNameFilter"));
            boolQueryBuilder.filter(countryNameQueryBuild);
        }

        if (!StringUtils.isEmpty(search.get("type"))){
            //如果电影类型不为空
            TermQueryBuilder countryNameQueryBuild=QueryBuilders.termQuery("state",search.get("type"));
            boolQueryBuilder.filter(countryNameQueryBuild);
        }

        //3.设置分页查询
        PageRequest page=null;
        if (!StringUtils.isEmpty(search.get("currentPage"))&&!StringUtils.isEmpty(search.get("pageSize"))){
            Integer currentPage = (Integer) search.get("currentPage");
            Integer pageSize = (Integer) search.get("pageSize");
            page=PageRequest.of(currentPage-1,pageSize);
        }else {
            page=PageRequest.of(0,5);
        }

//        //聚合 地区和类别
//        TermsAggregationBuilder typeAggBuilder= AggregationBuilders.terms("filmType_agg")
//                .field("typeName");
//
//        TermsAggregationBuilder countryAggBuilder= AggregationBuilders.terms("countryName_agg")
//                .field("countryName");

        //4.集中所有条件
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withPageable(page);

        //排序
        SortBuilder timeSortBuild=null;
        if (!StringUtils.isEmpty(search.get("timeSortType"))){
            //如果时间不为空，进行排序
            if ("desc".equals(search.get("timeSortType"))){
                //升序
                timeSortBuild= SortBuilders.fieldSort("uploadTime").order(SortOrder.DESC);
            }else {
                timeSortBuild=SortBuilders.fieldSort("uploadTime").order(SortOrder.ASC);
            }
        }

        SortBuilder scoreSortBuild=null;
        if (!StringUtils.isEmpty(search.get("scoreSortType"))){
            //如果分数排序信息不为空
            if ("desc".equals(search.get("scoreSortType"))){
                scoreSortBuild=SortBuilders.fieldSort("score").order(SortOrder.DESC);
            }else {
                scoreSortBuild=SortBuilders.fieldSort("score").order(SortOrder.ASC);
            }
        }
        if (scoreSortBuild!=null){
            builder.withSort(scoreSortBuild);
        }
        if (timeSortBuild!=null){
            builder.withSort(timeSortBuild);
        }
        NativeSearchQuery nativeSearchQuery = builder.build();
        SearchHits<ESMovie> resultHits = template.search(nativeSearchQuery, ESMovie.class);//获取到过滤的信息
        if (resultHits==null){
            return new PageResultVO<ESMovie>(false,"查询失败");
        }
        //5.获取总记录数
        long totalHits = resultHits.getTotalHits();
        //6.获取当前页数据
        List<SearchHit<ESMovie>> searchHits = resultHits.getSearchHits();
        List<ESMovie> esMovieList=new ArrayList<>();//设置一个集合装载数据
        searchHits.forEach(hit->{
            //读取文档信息
            ESMovie content = hit.getContent();
            esMovieList.add(content);
        });
//        //获取聚合信息
//        Aggregations aggregations = resultHits.getAggregations();
//        //获得电影类型
//        Terms typeNameAgg = aggregations.get("filmType_agg");
//        List<? extends Terms.Bucket> buckets = typeNameAgg.getBuckets();
//        List<String> typeNameList=new ArrayList<>();
//        buckets.forEach(type->{
//            typeNameList.add(type.getKeyAsString());
//        });
//        //地区类型
//        Terms countryNameAgg = aggregations.get("countryName_agg");
//        List<? extends Terms.Bucket> countryNameAggBuckets = countryNameAgg.getBuckets();
//        List<String> countryNameList=new ArrayList<>();
//        countryNameAggBuckets.forEach(type->{
//            countryNameList.add(type.getKeyAsString());
//        });

        //查询类型列表 并加入Redis缓存
        List<String> typeNameList=new ArrayList<>();
        List<String> countryNameList=new ArrayList<>();
        BoundListOperations<String, String> typeNameListOps = stringRedisTemplate.boundListOps("typeNameListOps");
        BoundListOperations<String, String> countryNameListOps = stringRedisTemplate.boundListOps("typeCountryListOps");
        List<String> range = typeNameListOps.range(0, -1);
        if (range.size()==0){
            //无值
            List<Type> typeList = movieMapper.selectTypeNameList();
            typeList.forEach(list->{
                String typeName = list.getTypeName();
                typeNameListOps.leftPush(typeName);
            });
        }else {
            //直接从Redis中查
            typeNameList = typeNameListOps.range(0, -1);
        }
        //查询国家列表 并加入Redis缓存中
        List<String> range1 = countryNameListOps.range(0, -1);
        if (range1.size()==0){
            List<Country> countryList = movieMapper.selectcountryNameList();
            countryList.forEach(list->{
                String countryName = list.getCountryName();
                countryNameListOps.leftPush(countryName);
            });
        }else {
            //直接从Redis中查
            countryNameList = countryNameListOps.range(0, -1);
        }
        SearchResultVo searchResultVo = new SearchResultVo();
        searchResultVo.setFlag(true);
        searchResultVo.setMsg("查询成功");
        searchResultVo.setTotal(totalHits);
        searchResultVo.setData(esMovieList);
       searchResultVo.setTypeList(typeNameList);
       searchResultVo.setCountryList(countryNameList);
        return  searchResultVo;
    }

    @Override
    public Result movieKeywordSearch(Map searchMap) {
        //1.设置查询条件
        MatchQueryBuilder movieName = QueryBuilders.matchQuery("movieName", searchMap.get("keyword"));
        //2.设置分页
        int page = 0;
        int limit = 5;
        if (!StringUtils.isEmpty(searchMap.get("page"))) {//当前页数
            page = (int) searchMap.get("page");
            page--;
        }
        if (!StringUtils.isEmpty(searchMap.get("limit"))) {//当前页数
            limit = (int) searchMap.get("limit");
        }
        PageRequest pageRequest = PageRequest.of(page, limit);
        //3.设置高亮
        HighlightBuilder highlightBuilder = getHighlightBuilder("movieName");
        NativeSearchQuery build = new NativeSearchQueryBuilder()
                .withQuery(movieName)
                //设置分页
                .withPageable(pageRequest)
                .withHighlightBuilder(highlightBuilder)//设置高亮
                .build();
        SearchHits<ESMovie> res = template.search(build, ESMovie.class);
        //获取当前页信息
        List<ESMovie> movieList = new ArrayList<>();
        long totalHits = res.getTotalHits();
        List<SearchHit<ESMovie>> hits = res.getSearchHits();
        hits.forEach(hit -> {
            ESMovie content = hit.getContent();
            //取高亮的信息 将原来的 替换
            Map<String, List<String>> map = hit.getHighlightFields();
            Set<String> keySet = map.keySet();
            keySet.forEach(key -> {
                if (key.equals("movieName")) {
                    String s = map.get(key).get(0);
                    content.setMovieName(s);
                }
            });
            movieList.add(content);
        });
        Map map = new HashMap(){{
            put("movieList",movieList);
            put("movieTotal",totalHits);
        }};
        return new Result(true,"成功",map);
    }

    /**
     * 上架商品
     * @param movieId
     * @throws Exception
     */

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Value("${topic}")
    private String TOPIC;

    @Value("${tag}")
    private String TAG;

    @Override
    @Transactional
    public Result audio(String movieId) throws Exception {
        //先根据movieId获取到数据库中的数据
        Movie movie = movieMapper.findMovieById(movieId);
        if (movie==null){
            return new Result<>(false,"参数异常");
        }
        //修改商品订单审核状态
        movie.setState("0");
        //修改数据库中的状态
        movieMapper.updateState(movie);

        //发送消息,异步处理,同步索引库
        SendResult sendResult = rocketMQTemplate.syncSend( TOPIC+":"+TAG , movie);
        SendStatus sendStatus = sendResult.getSendStatus();
        if (sendStatus==SendStatus.SEND_OK){
            return new Result(true,"同步成功");
        }else {
            //todo: 哪个消息什么时间 发送失败  补偿(将数据存数据库)
            return new Result(false,"同步失败");
        }

    }

    @Override
    public void addFilmToES(Movie spu) throws Exception {
            //添加到ES中
        ESMovie esMovie=new ESMovie();
        esMovie.setMovieId(spu.getMovieId());
        esMovie.setMovieName(spu.getMovieName());
        esMovie.setMoviePic(spu.getMoviePic());
        esMovie.setState(spu.getState());
        esMovie.setPrice(spu.getPrice());
        esMovie.setScore(spu.getScore());
        esMovie.setUploadTime(spu.getUploadTime());
        esMovie.setTotalTime(spu.getTotalTime());
        esMovie.setStartName(spu.getStar().getStarName());
        esMovie.setSex(spu.getStar().getSex());
        esMovie.setBirth(spu.getStar().getBirth());
        esMovie.setType(spu.getStar().getType());//演员类型
        esMovie.setNationlity(spu.getStar().getNationality());
        List<String> typeName =new ArrayList<>();
        List<String> typeNameList = spu.getType().getTypeNameList();
        StringBuilder typeNameBuilder=new StringBuilder();
        typeNameList.forEach(type->{
            typeNameBuilder.append(type);
        });
        typeNameBuilder.append("/恐怖");
        String TypetoString = typeNameBuilder.toString();
        esMovie.setTypeName(TypetoString);//电影类型
        esMovie.setCountryName(spu.getCountry().getCountryName());
        template.save(esMovie);
    }


    // 设置高亮字段
    private HighlightBuilder getHighlightBuilder(String... fields) {
        // 高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        for (String field : fields) {
            highlightBuilder.field(field);//高亮查询字段
        }
        highlightBuilder.requireFieldMatch(false);     //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style=\"color:red\">");   //高亮设置
        highlightBuilder.postTags("</span>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

        return highlightBuilder;
    }

}
